﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace SwiftMVVM.ViewModel
{
    /// <summary>
    /// This class provides the basic plumbing for NotifyPropertyChanged/Changing.
    /// We don't require that you derive your class from this, it's just here to keep the 
    /// plumbing out of your object. The change tracker will work fine with objects that
    /// implement INotifyPropertyChanged and INotifyPropertyChanging directly
    /// </summary>
    public abstract class BindingObject : INotifyPropertyChanged, INotifyPropertyChanging
    {
        /// <summary>
        /// Fired when a property on this object is changed.
        /// It is up to the derived class to call OnPropertyChanged
        /// to pass on the notification
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fired when a property on this object is about to chang.
        /// It is up to the derived class to call OnPropertyChanging
        /// to pass on the notification
        /// </summary>
        public event PropertyChangingEventHandler PropertyChanging;

        /// <summary>
        /// Fires off the PropertyChanging event
        /// </summary>
        /// <param name="propertyName">The name of the property about to change</param>
        protected void RaisePropertyChanging(string propertyName)
        {
            OnPropertyChanging(propertyName);
        }

        /// <summary>
        /// Fires off the PropertyChanged event
        /// </summary>
        /// <param name="propertyName">The name of the property that changed</param>
        protected void RaisePropertyChanged(string propertyName)
        {
            OnPropertyChanged(propertyName);
        }

        /// <summary>
        /// Called when a property is about to change
        /// </summary>
        /// <param name="propertyName">The name of the property about to change</param>
        protected virtual void OnPropertyChanging(string propertyName)
        {
            var handler = this.PropertyChanging;
            if (handler != null)
            {
                handler(this, new PropertyChangingEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Called when a property has just changed
        /// </summary>
        /// <param name="propertyName">The name of the property that just changed</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Allows you to set a field variable on your class. Only time you may want to 
        /// use this, is when you have a property that cannot be wrapped, however you want 
        /// it to use the same logic that the wrapped properties do for firing property
        /// change notifications
        /// </summary>
        /// <typeparam name="T">The type of the object being set</typeparam>
        /// <param name="emptyAction">In order to guarantee we always have the right property name, 
        /// pass an empty delegate in and we'll figure it out</param>
        /// <param name="field">The field that will be updated</param>
        /// <param name="newValue">The new value</param>
        /// <returns>True, if the values were different and a prop change notification sent, false otherwise</returns>
        protected bool SetPropertyValue<T>(Action emptyAction, ref T field, T newValue) where T : struct
        {
            if (emptyAction == null) { throw new ArgumentNullException("emptyAction"); }

            //Let's determine if the value has changed
            if (NotifyProxyTypeManager.Compare<T>(field, newValue))
            {
                //Slight speed penalty here, but it prevents us from getting the 
                //wrong property name (maintainability > speed here)
                var methodName = emptyAction.Method.Name;
                var propertyName = methodName.Substring(5, methodName.LastIndexOf('>') - 5);
                //By firing OnPropertyChanging here we allow listeners to 
                //retrieve our property value before it changes
                RaisePropertyChanging(propertyName);
                field = newValue;
                RaisePropertyChanged(propertyName);
                return true;
            }
            return false;
        }
    }
}
